#    Rubatom
#    Copyright (c) 2014 Sebastian Dufner
#
#    This file is part of Rubatom.
#
#    Rubatom is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    Rubatom is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.

module Rubatom    # :nodoc: all
  module ShipClassifierMethods

    def docking_area_multiplier;      343 ; end
    def docking_enhancer_multiplier;  70  ; end
    def turret_area_multiplier;       150 ; end
    def turret_enhancer_multiplier;   42  ; end

  protected

    def weapon_score
      amc_score + d1000_score + sdbb_score + sdkb_score + turret_score
    end

    def turret_score
      (turret_enhancer_count * turret_area_multiplier +
          turret_module_count * turret_enhancer_multiplier) / mass
    end

    def turret_module_count
      @turret_module_count ||= @structure.count {|b| b.id == 7 if b }
    end

    def turret_enhancer_count
      @turret_enhancer_count ||= @structure.count {|b| b.id == 88 if b }
    end

    def docking_score
      (docking_module_count * docking_area_multiplier +
          docking_enhancer_count * docking_enhancer_multiplier) / mass
    end

    def docking_module_count
      @docking_module_count = @structure.count {|b| b.id == 289 if b }
    end

    def docking_enhancer_count
      @docking_enhancer_count = @structure.count {|b|  b.id == 290 if b }
    end

    def engine_score
      @engine_score ||= @structure.count {|b| b.id == 8 if b } / mass
    end

    def storage_score
      @storage_score ||= @structure.count {|b| b.id == 120 if b } / mass
    end

    def stealth_score
      return @stealth_score if @stealth_score

      @stealth_score = 0
      @stealth_score += powergen_score if
        @structure.find_index {|b| b.id == 22 if b }
      @stealth_score += powergen_score if
        @structure.find_index {|b| b.id == 15 if b }

      @stealth_score
    end

    def amc_score
      @amc_score ||= @structure.count {|b| b.id == 16 if b } / mass
    end

    def missile_score
      d1000_score + sdbb_score + sdkb_score
    end

    def d1000_score
      @d1000_score ||= @structure.count {|b| b.id == 32 if b } / mass
    end

    def sdbb_score
      @sdbb_score ||= @structure.count {|b| b.id == 48 if b } / mass
    end

    def sdkb_score
      @sdkb_score ||= @structure.count {|b| b.id == 40 if b } / mass
    end

    def shield_score
      @shield_score ||= @structure.count {|b| b.id == 3 if b } / mass
    end

    def power_score
      powergen_score + powertank_score
    end

    def powergen_score
      @powergen_score ||= @structure.count {|b| b.id == 2 if b } / mass
    end

    def powertank_score
      @powertank_score ||= @structure.count {|b| b.id == 331 if b } / mass
    end

    def salvage_score
      @salvage_score ||= @structure.count {|b| b.id == 24 if b } / mass
    end

    def utility_score
      powersupply_score + powerdrain_score + repair_score
    end

    def repair_score
      @repair_score ||= @structure.count {|b| b.id == 30 if b } / mass
    end

    def powersupply_score
      @powersupply_score ||= @structure.count {|b| b.id == 335 if b } / mass
    end

    def powerdrain_score
      @powerdrain_score ||= @structure.count {|b| b.id == 333 if b } / mass
    end

    def pulse_score
      @pulse_score ||= @structure.count {|b| b.id == 345 if b } / mass
    end

    def disintegrator_score
      @disintegrator_score ||= @structure.count {|b| b.id == 14 if b } / mass
    end

    def offense_score
      weapon_score + turret_score
    end

    def shape_score
      return @shape_score if @shape_score

      id = proc {|x| x }

      # We reconstruct the shape of the spaceship by filling a cuboid with ones
      # and setting the positions that are also empty in the projections to nil.
      x_size = chassis_projection_y.size
      y_size = chassis_projection_x.size
      z_size = chassis_projection_x[0].size
      shape = Array.new(x_size) { Array.new(y_size) { Array.new(z_size, 1) }}

      x_size.times do |x|
        y_size.times do |y|
          z_size.times do |z|
            shape[x][y][z] = nil unless chassis_projection_x[y][z] &&
              chassis_projection_y[x][z] && chassis_projection_z[x][y]
          end
        end
      end

      # Now we divide the number of blocks in the original structure by the
      # number of elements in the reconstructed shape.
      @shape_score ||= mass * 10 / (shape
            .map {|plane| plane.map {|row| row.count &id }
            .inject {|a, b| a + b }}
            .inject {|a, b| a + b} -
          docking_enhancer_count - docking_module_count)
    end

    def chassis_projection_x
      @chassis_projection_x ||= @structure.projection(:x).freeze
    end

    def chassis_projection_y
      @chassis_projection_y ||= @structure.projection(:y).freeze
    end

    def chassis_projection_z
      @chassis_projection_z ||= @structure.projection(:z).freeze
    end

    def mass; @mass ||= @structure.mass; end
    def length; @length ||= @structure.length; end
    def height; @height ||= @structure.height; end
    def width; @width ||= @structure.width; end
    def max_dim; @max_dim ||= [length, height, width].max; end

  private

    def chassis_series_string(choices, score, string = '')
      return string if score <= 0 && string != ''

      index = score % choices.size
      chassis_series_string(
            choices, (score / choices.size), string.concat(choices[index]))
    end

    def chassis_series_scores(chassis, factor = 5, threshold = 9)
      a, b = (chassis.size / factor), (chassis[0].size / factor)
      scale = Array.new(a) { Array.new(b) }

      0.upto(a - 1) do |i|
        0.upto(b - 1) do |j|
          count = 0
          it, jt = i * factor, j * factor

          it.upto(it + factor - 1) do |ii|
            jt.upto(jt + factor - 1) do |jj|
              count += 1 if chassis[ii][jj]
            end
          end

          scale[i][j] = 1 if count > threshold
        end
      end

      return chassis_scores(scale)
    end

    def chassis_scores(chassis)
      sum2, sum, num = 0, 0, 0

      1.upto(chassis.size - 2) do |i|
        1.upto(chassis[0].size - 2) do |j|
          if chassis[i][j]
            value = 0

            value += 1 unless chassis[i - 1][j]
            value += 1 unless chassis[i + 1][j]
            value += 1 unless chassis[i][j - 1]
            value += 1 unless chassis[i][j + 1]

            sum2 += value * value
            sum  += value
            num  += 1 unless value == 0
          end
        end
      end

      return sum2, sum, num
    end

    def chassis_series_components
      [
        "C", "K", "Q", "X", "U", "D", "J", "A", "E", "F", "N", "W", "V",
        "Y", "G", "P", "I", "H", "M", "T", "Z", "L", "S", "R", "B", "O"
      ]
    end
  end
end
